#include <scdl/parser/internal.hpp>
#include <scdl/parser/Symbol.hpp>
#include <scdl/parser/StringMap.hpp>

#include <cstring>
#include <iostream>

using std::cout;
using std::cerr;
using namespace scdl;

Symbol *scdl::getType(const char *type)
{
        if (!symbolTable.find(type))
                return 0;

        Symbol *sym = symbolTable[type];
        if (sym->getTypeLevel() >= LEVEL_OBJECT)
                return 0;

        return sym;
}

bool scdl::checkType(Symbol *current, Symbol *target, bool strict)
{
        if (strict)
                return current == target;

        Symbol *type = current;
        while (type) {
                if (type == target)
                        return true;
                type = type->getType();
        }
        return false;
}

float scdl::resolveIdentifier(const char *id)
{
        Symbol *sym, *val;

        if (!symbolTable.find(id))
                goto exit;

        sym = symbolTable[id];
        if (sym->getCategory() != CATEGORY_DEFINITION)
                goto exit;

        val = static_cast<Identifier *>(sym)->getDef();
        if (val->getCategory() == CATEGORY_NUMBER) {
                return static_cast<Number *>(val)->getValue();
        }

exit:
        return 0.0f;
}

static float getNumber(Symbol *sym)
{
again:
        switch(sym->getCategory()) {
        case CATEGORY_NUMBER:
                return static_cast<Number *>(sym)->getValue();
        case CATEGORY_DEFINITION:
                sym = static_cast<Identifier *>(sym)->getDef();
                goto again;
        }
        return 0.0f;
}

Symbol *scdl::calculateConstant(char op, Symbol *leftSym, Symbol *rightSym)
{
        float left = getNumber(leftSym), right = getNumber(rightSym);
        float result = 0.0f;

        switch(op) {
        case '+':
                result = left + right;
                break;
        case '-':
                result = left - right;
                break;
        case '*':
                result = left * right;
                break;
        case '/':
                result = left / right;
                break;
        }

        Number *ret = new Number;
        ret->setType(getIdentifier("Number"));
        ret->setValue(result);
        return ret;
}

bool scdl::makeAssumption(Identifier *id, Symbol *type, Symbol *value)
{
        if (!type) {
                position(id);
                cerr << "error: Assumptions must have a type.\n";
                return false;
        }

        id->setType(type);
        return true;
}

bool scdl::makeDefinition(Identifier *id, Symbol *type, Symbol *value)
{
        Symbol *expected = type;
        if (!value) {
                position(id);
                cerr << "error: Definitions must have a value.\n";
                return false;
        }

        type = value->getType(expected);
        if (!type) {
                position(id);
                cerr << "error: Cannot determine type of the value for \""
                     << id->getName() << "\".\n";
                return false;
        }

        if (expected != 0 && !checkType(type, expected, true)) {
                position(id);
                cerr << "error: Types conflict.\n";
                position(id);
                cerr << "\texpected: "
                     << static_cast<NamedSymbol *>(expected)->getName()
                     << "\n";
                position(id);
                cerr << "\timplied: "
                     << static_cast<NamedSymbol *>(type)->getName()
                     << "\n";
                return false;
        }

        id->setType(type);
        id->setDef(value);
        return true;
}

bool scdl::makeBinder(Identifier *id, Symbol *type, const char *binder)
{
        if (!type) {
                position(id);
                cerr << "error: Binders must have a type.\n";
                return false;
        }
        if (!binder) {
                position(id);
                cerr << "error: Binders must have a value.\n";
                return false;
        }

        id->setType(type);
        id->setBinder(binder);
        return true;
}

bool scdl::makeInductive(const char *name, Symbol *type, List *ind_list)
{
        /* Create the type first */
        Identifier *itype = new Identifier;
        itype->setPosition(ind_list);
        itype->setName(name);
        itype->setType(type);
        bool itypeUsed = false;

        Identifier *id;
        int num = ind_list->getNrElements();
        for (int i = 0; i < num; i++) {
                Symbol *sym = ind_list->getElement(i);
                id = new Identifier;
                id->setPosition(sym);
                switch(sym->getCategory()) {
                case CATEGORY_ASSUMPTION:
                case CATEGORY_DEFINITION:
                case CATEGORY_INTERFACE:
                case CATEGORY_INDUCTIVE_CTOR:
                case CATEGORY_IDENTIFIER_LIST:
                        if (!type) {
                                position(id);
                                cerr << "error: Cannot determine type for "
                                     << static_cast<NamedSymbol *>(sym)->getName()
                                     << "\n";
                                delete id;
                                id = 0;
                        } else {
                                id->setName(static_cast<NamedSymbol *>(sym)->getName());
                                id->setType(itype);
                                id->setInductiveCtorValue(i);
                                itypeUsed = true;
                        }
                        break;
                case CATEGORY_PRODUCT:
                        if (type) {
                                position(id);
                                cerr << "error: unwanted type for inductive products.\n";
                                delete id;
                                id = 0;
                        } else {
                                id->setName(name);
                                id->setType(sym);
                                id->setInductiveCtorValue(i);
                        }
                        break;
                default:
                        delete id;
                        id = 0;
                }

                addIdentifier(id);
        }

        if(itypeUsed)
                addIdentifier(itype);
        else
                delete itype;
        return itypeUsed;
}

bool scdl::resolveParameter(Symbol *binder, Symbol *param)
{
        if (param->getCategory() != CATEGORY_PARAMETER)
                return true;
        if (binder->getCategory() != CATEGORY_INTERFACE)
                return false;

        Identifier *id = static_cast<Identifier *>(binder);
        Parameter *p = static_cast<Parameter *>(param);
        Record *record = id->getRecord();
        int num = record->getNrElements();
        for (int i = 0; i < num; i++) {
                Identifier *entry = record->getEntry(i);
                if (strcmp(p->getName(), entry->getName()) == 0) {
                        p->setType(entry->getType());
                        p->setIndex(i);
                        break;
                }
        }
        return true;
}

bool scdl::touchList(const char *name)
{
        if (symbolTable.find(name))
                return false;

        IdentifierList *list = new IdentifierList;
        list->setName(name);
        symbolTable[name] = list;
        return true;
}

bool scdl::addIdentifier(Identifier *id)
{
        if (!id)
                return false;

        const char *name = id->getName();
        if (symbolTable.find(name)) {
                Symbol *sym = symbolTable[name];
                if (sym->getCategory() != CATEGORY_IDENTIFIER_LIST) {
                        position(id);
                        cerr << "error: \"" << name << "\" has already been used.\n";
                        return false;
                }
                IdentifierList *list = static_cast<IdentifierList *>(sym);
                list->pushElement(id);
        } else {
                IdentifierList *list = new IdentifierList;
                list->setName(id->getName());
                list->pushElement(id);
                symbolTable[name] = list;
        }
        return true;
}

Identifier *scdl::getIdentifier(const char *name)
{
        if (!symbolTable.find(name))
                return 0;

        Symbol *sym = symbolTable[name];
        if (sym->getCategory() != CATEGORY_IDENTIFIER_LIST)
                return 0;
        IdentifierList *list = static_cast<IdentifierList *>(sym);
        if (list->getNrElements() != 1)
                return 0;
        return static_cast<Identifier *>(list->getElement(0));
}

void scdl::position(Symbol *sym)
{
        cerr << sym->file() << ":"
             << sym->line() << ":"
             << sym->column() << ": ";
}
